在使用 PowerShell 執行對多個目標的任務時,選擇適當的技巧可以大幅提高效率和性能。今天將深入探討兩種主要的處理方式:
批次處理 cmdlet 是指那些能夠直接對多個物件進行操作的命令。例如,Get-Service、Stop-Process 等 cmdlet 本身就支援對多個目標( batches, Collections)同時執行操作。這種方法通常是最有效率且簡潔的。
Stop-Process -Name "notepad"
Copy-Item -Path C:\Source\*.txt -Destination C:\Destination\
上述範例中,Stop-Process 和 Copy-Item 都能直接接受多個物件,無需額外的迴圈或列舉。
我打算用 Get-ChildItem 去搜尋在目前資料夾底下的 txt,並透過 pipeline 傳遞給 Copy-Item 後,Copy-Item 將 Get-ChildItem 的結果 Copy 到子資料夾 temp 底下。先條列出目前資料夾成員及狀態:
PS /Users/kanglin/code/30days> ls -l
total 112
-rw-r--r--@ 1 kanglin staff 2943 9 26 10:47 Untitled.ipynb
-rw-r--r--@ 1 kanglin staff 63 9 24 22:14 aliases.txt
-rw-r--r--@ 1 kanglin staff 90 9 24 13:09 modules.txt
-rw-r--r--@ 1 kanglin staff 29233 9 26 10:43 result.json
drwxr-xr-x@ 2 kanglin staff 64 10 1 13:10 temp
-rw-r--r--@ 1 kanglin staff 11667 9 30 17:01 whitelist.ipynb
PS /Users/kanglin/code/30days> ls -l temp
total 0
透過 Get-ChildItem 搜尋出所有的 txt,並透過 Get-Member 確認其 Type:
PS /Users/kanglin/code/30days> get-ChildItem -Name *.TXT
aliases.txt
modules.txt
PS /Users/kanglin/code/30days> get-ChildItem -Name *.TXT | gm
TypeName: System.String
當將 Get-ChildItem 的結果透過 pipeline 傳遞給 Copy-Item 操作時,你會發現即使操作完成,也不會有任何結果 output 到畫面上:
PS /Users/kanglin/code/30days> Get-ChildItem -Name *.TXT | Copy-Item -Destination temp
PS /Users/kanglin/code/30days> ls -l temp
total 16
-rw-r--r--@ 1 kanglin staff 63 9 24 22:14 aliases.txt
-rw-r--r--@ 1 kanglin staff 90 9 24 13:09 modules.txt
但,你可以透過在 Copy-Item 操作命令中加入 -PassThru
的參數,參數解釋請參考:
get-help copy-item -Parameter Pass*
-PassThru <System.Management.Automation.SwitchParameter>
Returns an object that represents the item with which you're working. By default, this cmdlet doesn't generate any output.
Required? false
Position? named
Default value False
Accept pipeline input? False
Accept wildcard characters? false
加入後執行結果如下:
PS /Users/kanglin/code/30days> Get-ChildItem -Name *.TXT | Copy-Item -Destination temp -PassThru
Directory: /Users/kanglin/code/30days/temp
UnixMode User Group LastWriteTime Size Name
-------- ---- ----- ------------- ---- ----
-rw-r--r-- kanglin staff 2024/9/24 22:14 63 aliases.txt
-rw-r--r-- kanglin staff 2024/9/24 13:09 90 modules.txt
在某些情況下,我們只能使用會產生物件的 cmdlet,可是卻找不到合適用來傳遞的批次處理 cmdlet,來對這些物件進行一些操作。我們也遇過 cmdlet 不接受來自管線的任何輸出的情況。
當批次處理 cmdlet 無法滿足需求時,可以使用物件列舉的方法,透過迴圈逐一處理每個物件。
透過 foreach
$services = Get-Service -Name "Service1", "Service2", "Service3"
foreach ($service in $services) {
$service.Stop()
}
透過 ForEach-Object
Get-ChildItem -Path C:\Logs\*.log | ForEach-Object {
# 壓縮並移動檔案
Compress-Archive -Path $_.FullName -DestinationPath "C:\Archive\$($_.Name).zip"
Remove-Item -Path $_.FullName
}
以下是 foreach
和 ForEach-Object
的比較表:
比較項目 | foreach |
ForEach-Object |
---|---|---|
類型 | 語法關鍵字 | Cmdlet(命令) |
行為 | 先將所有集合載入記憶體,逐項操作 | 逐一從管線中接收物件並即時處理 |
資料處理方式 | 適合處理已完全載入到變數中的集合 | 適合從管線中逐個處理物件,不需一次載入所有物件 |
記憶體使用 | 需要一次載入整個集合,對大數據處理效能較差 | 不需要一次載入所有物件,適合大數據處理 |
使用場景 | 適合處理已存在的集合 | 適合處理從管線逐個傳入的物件 |
語法風格 | 類似傳統迴圈語法 | 常用於 PowerShell 的管線處理 |
透過 Measure-Command
cmdlet,可用來測量一個指令碼區塊的執行時間。
PS /Users/kanglin/code/30days> get-help Measure-Command
NAME
Measure-Command
SYNOPSIS
Measures the time it takes to run script blocks and cmdlets.
將 Get-Process 的結果存到 $allPorcess,目前 Process 數量為 465 個,每次遍例都暫停 0.01 秒,所以看到 Measure-Command 的結果裡呈現出耗時 5 秒是在預期內。
Measure-Command {
Get-Process | ForEach-Object {
Write-Host $_.Id
Start-Sleep 0.01
}
}
為 ForEach-Object 加上 -Parallel
,讓 PowerShell 使用並行方式處理多個物件,從原本的 5 秒,縮短成 1 秒。
Measure-Command {
Get-Process | ForEach-Object -Parallel {
Write-Host $_.Id
Start-Sleep 0.01
}
}
預設的並行數量限制是 5。這意味著同時最多會有 5 個平行執行的工作執行緒(threads)。可以使用 -ThrottleLimit
參數來調整這個限制:
Get-Process | ForEach-Object -Parallel {
Write-Host $_.Id
Start-Sleep 0.01
} -ThrottleLimit 10 # 調整並行限制為 10
Day 18 - CIM vs. WMI